﻿using System;
using System.Threading;
using System.Text.RegularExpressions;
using System.Xml;
using System.Diagnostics;
using System.IO;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

/*
 *  XsltDbClient 01.02.00
 *  
 * What's new (enhancements by Alberto Velo):
    * handle possible errors when saving file from VStudio2008 (maybe locked, retry)
    * new context menu
    * store last used login in config
    * allow to resize window
    * sort by columns (asc/desc)
    * new columns: TabID, Configuration Name, Service Name, Status (Draft|Published), IsSuperModule (modify GetModuleList on server)
 * 
 * 
 *  XsltDbClient 01.02.01
    * publish single module
    * statusbar
 * 
*/

namespace XsltDbClient
{
    public partial class Form1 : Form
    {
        Hashtable content = new Hashtable();
        RichXmlClient client;
        private int sortColumn = -1;

        static Form1()
        {
        }
        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 01.01.00.09: new columns
        /// </summary>
        private void RefreshList()
        {
            try
            {
                client = ServiceUtils.CreateClient(Settings.Portal, username, password);
                if (client == null)
                    return;
                DataTable dt = client.GetXsltDbList();
                lstModules.Items.Clear();
                foreach (DataRow dr in dt.Rows)
                {
                    ListViewItem lvi = null;

                    try
                    {
                        // if new columns supported
                        lvi = new ListViewItem(new string[]{
                            dr["ModuleID"].ToString(),
                            dr["TabID"].ToString(),
                            dr["TabName"].ToString(),
                            dr["TabTitle"].ToString(),
                            dr["ModuleTitle"].ToString(),
                            dr["PaneName"].ToString(),
                            dr["ConfigurationName"].ToString(),
                            dr["ServiceName"].ToString(),
                            dr["Status"].ToString(),
                            dr["IsSuper"].ToString()
                        });
                    }
                    catch (Exception)
                    {
                        //old version server
                        lvi = new ListViewItem(new string[]{
                            dr["ModuleID"].ToString(),
                            dr["TabName"].ToString(),
                            dr["TabTitle"].ToString(),
                            dr["ModuleTitle"].ToString(),
                            dr["PaneName"].ToString()
                        });

                    }

                    lvi.Tag = dr["ModuleID"];
                    lstModules.Items.Add(lvi);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString());
                MessageBox.Show("Can't connect to the portal. Check your settings");
                ex.ToString();
            }
        }

        string username = string.Empty;
        string password = string.Empty;
        private void Form1_Load(object sender, EventArgs e)
        {
            Login login = new Login();
            if (login.ShowDialog() == DialogResult.OK)
            {
                username = login.Username;
                password = login.Password;
                RefreshList();
                toolStripStatusLabel1.Text = "Ready.";
            }
            else
                this.Close();
        }

        private static string xslLargeHeaderFooter = @"
<!-- @@@==================================================== -->
<!-- @@@============= DO NOT EDIT THIS HEADER ============== -->
<!-- @@@==================================================== -->
<xsl:stylesheet version=""2.0""
   xmlns:xsl=""http://www.w3.org/1999/XSL/Transform""
   xmlns:msxsl=""urn:schemas-microsoft-com:xslt""
   exclude-result-prefixes=""msxsl mdo""
   xmlns:mdo=""urn:mdo""
>
  <xsl:output method=""html"" indent=""yes"" omit-xml-declaration=""yes""/>
  <xsl:template match=""/"">
<!-- @@@==================================================== -->
<!-- @@@================= PUT YOUR CODE HERE =============== -->

{0}

<!-- @@@==================================================== -->
<!-- @@@============= DO NOT EDIT THIS FOOTER ============== -->
<!-- @@@==================================================== -->
  </xsl:template>
</xsl:stylesheet>
";

        private static string xslSmallHeaderFooter = @"
<!-- @@@==================================================== -->
<!-- @@@============= DO NOT EDIT THIS HEADER ============== -->
<!-- @@@==================================================== -->
<xsl:stylesheet version=""2.0""
   xmlns:xsl=""http://www.w3.org/1999/XSL/Transform""
   xmlns:msxsl=""urn:schemas-microsoft-com:xslt""
   exclude-result-prefixes=""msxsl mdo""
   xmlns:mdo=""urn:mdo""
>
<!-- @@@==================================================== -->
<!-- @@@================ PUT YOUR CODE HERE ================ -->

{0}

<!-- @@@==================================================== -->
<!-- @@@============== DO NOT EDIT THIS FOOTER ============= -->
<!-- @@@==================================================== -->
</xsl:stylesheet>
";
        public enum XsltDbWrapper
        {
            None,
            Small,
            Large
        }

        public class FSWatcher
        {
            public string File = string.Empty;
            public XsltDbWrapper Wrapper = XsltDbWrapper.None;
            public int ModuleID = -1;
            public string Content = string.Empty;
            public FileSystemWatcher watcher;
        }

        Dictionary<int, FSWatcher> watchers = new Dictionary<int, FSWatcher>();

        /// <summary>
        /// Double-Click to Edit XSLT
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lstModules_ItemActivate(object sender, EventArgs e)
        {
            int ModuleID = Convert.ToInt32(lstModules.FocusedItem.Tag);
            toolStripStatusLabel1.Text = "Opening Module " + ModuleID.ToString() + "...";
            Update();
            OpenModule(ModuleID);
            toolStripStatusLabel1.Text = "Module " + ModuleID.ToString() + " opened";
        }

        private static string NormalizeText(string xsl)
        {
            xsl = xsl.Replace("\r", "");

            Regex leadNewLines = new Regex(@"^\s*\n+", RegexOptions.Multiline);
            while (leadNewLines.Match(xsl).Success)
                xsl = leadNewLines.Replace(xsl, "");

            Regex finNewLines = new Regex(@"\s*\n+\s*$", RegexOptions.Multiline);
            while (finNewLines.Match(xsl).Success)
                xsl = finNewLines.Replace(xsl, "");

            Regex emptyStr = new Regex(@"\n\s+\n", RegexOptions.Multiline);
            while (emptyStr.Match(xsl).Success)
                xsl = emptyStr.Replace(xsl, @"\n\n");

            xsl = "\n" + xsl;

            Regex leadSpace = new Regex(@"\n(\s*)");
            int len = int.MaxValue;
            foreach (Match m in leadSpace.Matches(xsl))
            {
                if (len > m.Groups[1].Length)
                    len = m.Groups[1].Length;
            }
            if (len > 0 && len != int.MaxValue)
            {
                Regex leadSpaceRep = new Regex(@"\n\s{1," + len + "}", RegexOptions.Multiline);
                xsl = leadSpaceRep.Replace(xsl, "\n");
            }

            xsl = xsl.Substring(1);

            return xsl;
        }

        private void JustOpenFile(FSWatcher w)
        {
            Process.Start(w.File);
        }

        private void Save(FSWatcher w)
        {
            string newxsl;
            int nTrySave = 0;

            //AVELO: manage possible error saving file (file in use)
            //by waiting a while and retrying
            doSave:

            try
            {
                using (StreamReader sr = new StreamReader(w.File))
                    newxsl = sr.ReadToEnd();    
            }
            catch (Exception e)
            {
                //file in use: retry for a couple of seconds, then give error
                if (nTrySave >= 20)
                {
                    DialogResult rc= MessageBox.Show(e.Message, "Cannot save file", MessageBoxButtons.RetryCancel);
                    if (rc == DialogResult.Retry)
                        goto doSave;
                    else
                        return;
                }
                else
                {
                    Thread.Sleep(100);
                    goto doSave;
                }

            }
           
            switch (w.Wrapper)
            {
                case XsltDbWrapper.Small:
                    {
                        Regex r = new Regex("<xsl:stylesheet[^>]*>", RegexOptions.Multiline);
                        Match m = r.Match(newxsl);
                        newxsl = newxsl.Substring(m.Index + m.Length);

                        r = new Regex("</xsl:stylesheet>");
                        m = r.Match(newxsl);
                        newxsl = newxsl.Substring(0, m.Index);
                    }
                    break;
                case XsltDbWrapper.Large:
                    {
                        Regex r = new Regex(@"<xsl:template[^>]*>",RegexOptions.Multiline);
                        Match m = r.Match(newxsl);
                        newxsl = newxsl.Substring(m.Index + m.Length);

                        r = new Regex(@"</xsl:template>");
                        m = r.Match(newxsl);
                        newxsl = newxsl.Substring(0, m.Index);
                    }
                    break;
            }

            Regex rx = new Regex(@"\s*<!-- @@@[^-]*-->\n");
            newxsl = rx.Replace(newxsl, string.Empty);
            newxsl = NormalizeText(newxsl);

            if ( w.Content != newxsl )
                client.SaveXsltDb(w.ModuleID, newxsl);

            w.Content = newxsl;

        }

        void watcher_Changed(object sender, FileSystemEventArgs e)
        {
            int ModuleID;
            if (int.TryParse(Path.GetFileNameWithoutExtension(e.FullPath), out ModuleID))
            {
                FSWatcher w = watchers[ModuleID];
                if (e.FullPath == w.File)
                {
                    Thread.Sleep(100);
                    Save(w);
                }
            }
        }

        private void toolRefresh_Click(object sender, EventArgs e)
        {
            toolStripStatusLabel1.Text = "Refreshing...";
            Update();
            lstModules.Items.Clear();
            RefreshList();
            toolStripStatusLabel1.Text = "Modules list refreshed";
        }

        private void toolSettings_Click(object sender, EventArgs e)
        {
            Settings s = new Settings();
            if (s.ShowDialog() == DialogResult.OK)
                RefreshList();
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            foreach (FSWatcher w in watchers.Values)
            {
                w.watcher.Dispose();
                File.Delete(w.File);
                Directory.Delete(Path.GetDirectoryName(w.File));
            }
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (watchers.Count > 0)
            {
                List<string> modules = new List<string>();
                foreach (FSWatcher w in watchers.Values)
                    modules.Add(w.ModuleID.ToString());

                DialogResult r = MessageBox.Show("the following modules are under control:\n\n" +
                    string.Join(", ", modules.ToArray()) +
                    "\n\nIs it OK to exit?", "XsltDb Rich Client",
                    MessageBoxButtons.YesNo, MessageBoxIcon.Question);

                e.Cancel = r == DialogResult.No;
            }
        }

        private void toolPublishAll_Click(object sender, EventArgs e)
        {
            toolStripStatusLabel1.Text = "Publishing...";
            Update();
            client.PublishAll();
            toolStripStatusLabel1.Text = "All Modules published";
        }


        /// <summary>
        /// Context-Menu to Edit XSLT
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void openToolStripMenuItem_Click(object sender, EventArgs e)
        {
            toolStripStatusLabel1.Text = "Opening...";
            Update();
            int ModuleID = Convert.ToInt32(lstModules.FocusedItem.Tag);
            OpenModule(ModuleID);
            toolStripStatusLabel1.Text = "Module " + ModuleID.ToString() + " opened";
        }

        /// <summary>
        /// Show Context menu
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void lstModules_MouseUp(object sender, MouseEventArgs e)
        {
            Control c = sender as Control;
            if (e.Button == MouseButtons.Right)
            {
                contextMenuStrip1.Show(c.PointToScreen(e.Location));
            }
        }


        /// <summary>
        /// Edit XSLT opening file with external application
        /// </summary>
        /// <param name="ModuleID"></param>
        private void OpenModule(int ModuleID)
        {
            if (watchers.ContainsKey(ModuleID))
            {
                JustOpenFile(watchers[ModuleID]);
                return;
            }

            FSWatcher w = new FSWatcher();
            w.ModuleID = ModuleID;
            watchers[ModuleID] = w;

            string dir = Path.GetTempFileName();
            File.Delete(dir);
            Directory.CreateDirectory(dir);
            w.File = Path.Combine(dir, ModuleID + ".xslt");

            string xsl = client.GetXsltDb(ModuleID);

            w.Wrapper = XsltDbWrapper.None;
            if (xsl.IndexOf("<xsl:stylesheet") < 0)
            {
                if (xsl.IndexOf("<xsl:template") < 0)
                {
                    w.Wrapper = XsltDbWrapper.Large;
                    xsl = string.Format(xslLargeHeaderFooter, xsl);
                }
                else
                {
                    w.Wrapper = XsltDbWrapper.Small;
                    xsl = string.Format(xslSmallHeaderFooter, xsl);
                }
            }

            xsl = NormalizeText(xsl);

            using (StreamWriter sw = new StreamWriter(w.File))
                sw.Write(xsl);

            w.watcher = new FileSystemWatcher(dir);
            w.watcher.EnableRaisingEvents = true;
            w.watcher.Changed += new FileSystemEventHandler(watcher_Changed);

            //todo: verify .xsl files are registered with some application
            //or catch exception and ask user to configure default app
            Process.Start(w.File);
        }

        /// <summary>
        /// Publish Module
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void publishToolStripMenuItem_Click(object sender, EventArgs e)
        {
            //Publish single module
            int ModuleID = Convert.ToInt32(lstModules.FocusedItem.Tag);

            DialogResult rc= MessageBox.Show("Publish this module (ModuleID " + ModuleID + ") ?", "Publish module", MessageBoxButtons.OKCancel);

            if (rc == DialogResult.OK)
            {
                toolStripStatusLabel1.Text = "Publishing Module " + ModuleID.ToString() + "...";
                Update();
                client.PublishModule(ModuleID);
                toolStripStatusLabel1.Text = "Module " + ModuleID.ToString() + " published";
            }
        }

        private void lstModules_ColumnClick(object sender, ColumnClickEventArgs e)
        {
            // Determine whether the column is the same as the last column clicked.
            if (e.Column != sortColumn)
            {
                // Set the sort column to the new column.
                sortColumn = e.Column;
                // Set the sort order to ascending by default.
                lstModules.Sorting = SortOrder.Ascending;
            }
            else
            {
                // Determine what the last sort order was and change it.
                if (lstModules.Sorting == SortOrder.Ascending)
                    lstModules.Sorting = SortOrder.Descending;
                else
                    lstModules.Sorting = SortOrder.Ascending;
            }

            // Call the sort method to manually sort.
            lstModules.Sort();
            // Set the ListViewItemSorter property to a new ListViewItemComparer
            // object.
            this.lstModules.ListViewItemSorter = new ListViewItemComparer(e.Column, lstModules.Sorting);
        }

        private void PublishAlltoolStripMenuItem_Click(object sender, EventArgs e)
        {
            toolStripStatusLabel1.Text = "Publishing...";
            Update();
            client.PublishAll();
            toolStripStatusLabel1.Text = "All Modules published";
        }


    }

    class ListViewItemComparer : IComparer
    {
        private int col;
        private SortOrder order;
        public ListViewItemComparer()
        {
            col = 0;
            order = SortOrder.Ascending;
        }
        public ListViewItemComparer(int column, SortOrder order)
        {
            col = column;
            this.order = order;
        }
        public int Compare(object x, object y) 
    {
        int returnVal= -1;
        returnVal = String.Compare(((ListViewItem)x).SubItems[col].Text,
                                ((ListViewItem)y).SubItems[col].Text);
        // Determine whether the sort order is descending.
        if (order == SortOrder.Descending)
            // Invert the value returned by String.Compare.
            returnVal *= -1;
        return returnVal;
    }
    }

}
